//=============================================================================
// CoolStorage.NET - .NET Object Relational Mapping Library 
//
// Copyright (c) 2004-2007 Philippe Leybaert
//
// Permission is hereby granted, free of charge, to any person obtaining a copy 
// of this software and associated documentation files (the "Software"), to deal 
// in the Software without restriction, including without limitation the rights 
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
// copies of the Software, and to permit persons to whom the Software is 
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in 
// all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//=============================================================================

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Reflection;
using System.Runtime.Serialization;

namespace Activa.CoolStorage
{
    public delegate void CSToArrayIterator<SourceType,TargetType>(SourceType objFrom , ref TargetType objTo);

	[Serializable]
    public abstract class CSList
    {
        public   abstract bool Save();

        internal abstract void SetRelation(CSObject relationObject, CSRelation relation);
        internal abstract void UpdateForeignKeys();
        internal abstract void Remove(CSObject obj);
    }

	[Serializable]
	public class CSList<ObjectType, KeyType> : CSList<ObjectType> where ObjectType : CSObject<ObjectType>
	{
		public CSList()
		{
		}

		public CSList(string filterExpression)
			: base(new CSFilter(filterExpression))
		{
		}

		public CSList(string filterExpression, string paramName, object paramValue)
			: base(filterExpression, new CSParameterCollection(paramName, paramValue))
		{
		}

		public CSList(string filterExpression, string paramName1, object paramValue1, string paramName2, object paramValue2)
			: base(filterExpression, new CSParameterCollection(paramName1, paramValue1, paramName2, paramValue2))
		{
		}

		public CSList(string filterExpression, string paramName1, object paramValue1, string paramName2, object paramValue2, string paramName3, object paramValue3)
			: base(filterExpression, new CSParameterCollection(paramName1, paramValue1, paramName2, paramValue2, paramName3, paramValue3))
		{
		}

		public CSList(string filterExpression, CSParameterCollection parameters)
			: base(new CSFilter(filterExpression, parameters))
		{
		}

		public CSList(string filterExpression, params CSParameter[] parameters)
			: base(new CSFilter(filterExpression, parameters))
		{
		}

		public CSList(CSFilter filter)
			: base(filter)
		{
		}

		public CSList(CSList<ObjectType> sourceCollection)
			: base(sourceCollection)
		{
		}

		public ObjectType GetByKey(KeyType key)
		{
		    return base.GetByKey(key);
		}

        public new CSList<ObjectType, KeyType> FilteredBy(string filter)
        {
            return (CSList<ObjectType, KeyType>) base.FilteredBy(filter);
        }

        public new CSList<ObjectType, KeyType> FilteredBy(string filter, string paramName, object paramValue)
        {
            return (CSList<ObjectType, KeyType>) base.FilteredBy(filter, paramName, paramValue);
        }

        public new CSList<ObjectType, KeyType> FilteredBy(Predicate<ObjectType> predicate)
        {
            return (CSList<ObjectType, KeyType>)base.FilteredBy(predicate);
        }
	    
   		public new CSList<ObjectType,KeyType> OrderedBy(string orderBy)
   		{
            return (CSList<ObjectType, KeyType>) base.OrderedBy(orderBy);
   		}
	}

	[Serializable]
	public class CSList<ObjectType> : CSList , ITypedList , IList<ObjectType>, IBindingList , IListSource where ObjectType : CSObject<ObjectType>
	{
		[NonSerialized]
		private CSSchema      _schema;

		private bool          _populated;
		private List<ObjectType> _objectArray;
		private List<ObjectType> _removedObjects; // Only used for pure many-to-many relations
		private List<ObjectType> _addedObjects; // Only used for pure many-to-many relations

		[NonSerialized]
		private Dictionary<object,ObjectType> _objectMap;
		[NonSerialized]
		private CSRelation    _relation;
		[NonSerialized]
		private CSObject      _relationObject;
	    private int           _startRecord;
		private int           _maxRecords;
		private string        _orderBy = "";

        private CSFilter _filter = CSFilter.None;

		[NonSerialized]
        private Predicate<ObjectType> _filterPredicate;

		[OnDeserializing]
		private void BeforeDeserializing(StreamingContext context)
		{
			_schema = CSSchema.Get(typeof(ObjectType));

			if (_schema.DefaultSortExpression != null && _schema.DefaultSortExpression.Length > 0)
				_orderBy = _schema.DefaultSortExpression;
		}

		[OnDeserialized]
		private void AfterDeserializing(StreamingContext context)
		{
			if (_schema.KeyColumns.Count == 1)
			{
				string columnName = _schema.KeyColumns[0].Name;

				_objectMap = new Dictionary<object, ObjectType>();

				foreach (ObjectType csObject in _objectArray)
					_objectMap.Add(csObject.Data["#" + columnName].Value, csObject);
			}
		}

		public CSList()
		{
			_schema = CSSchema.Get(typeof(ObjectType));

			if (_schema.DefaultSortExpression != null && _schema.DefaultSortExpression.Length > 0)
				_orderBy = _schema.DefaultSortExpression;
		}

        public CSList(string filterExpression)
            : this(new CSFilter(filterExpression))
        {
        }

		public CSList(string filterExpression, string paramName , object paramValue)
	        : this(filterExpression , new CSParameterCollection(paramName, paramValue))
		{
		}

		public CSList(string filterExpression, string paramName1, object paramValue1, string paramName2, object paramValue2)
			: this(filterExpression, new CSParameterCollection(paramName1, paramValue1, paramName2, paramValue2))
		{
		}

		public CSList(string filterExpression, string paramName1, object paramValue1, string paramName2, object paramValue2, string paramName3, object paramValue3)
			: this(filterExpression, new CSParameterCollection(paramName1, paramValue1, paramName2, paramValue2, paramName3, paramValue3))
		{
		}

		public CSList(string filterExpression, CSParameterCollection parameters)
			: this(new CSFilter(filterExpression, parameters))
		{
		}

		public CSList(string filterExpression, params CSParameter[] parameters)
			: this(new CSFilter(filterExpression, parameters))
		{
		}

		public CSList(CSFilter filter)
			: this()
		{
			_filter = filter;
		}

		public CSList(CSList<ObjectType> sourceCollection)
			: this()
		{
			_orderBy = sourceCollection._orderBy;
			_maxRecords = sourceCollection._maxRecords;
		    _startRecord = sourceCollection._startRecord;

			_filter = sourceCollection._filter;
			_relation = sourceCollection._relation;
			_relationObject = sourceCollection._relationObject;
		}

		public ObjectType this[int i]
		{
			get
			{
				Populate();

				return _objectArray[i];
			}
		    set
		    {
                throw new NotImplementedException();
		    }
		}

		public CSList<ObjectType> FilteredBy(CSFilter filter)
		{
			CSList<ObjectType> newCollection = (CSList<ObjectType>)Activator.CreateInstance(GetType());

			newCollection._orderBy = _orderBy;
			newCollection._maxRecords = _maxRecords;
		    newCollection._startRecord = _startRecord;
			newCollection._filter = _filter.And(filter);
			newCollection._relation = _relation;
			newCollection._relationObject = _relationObject;

			return newCollection;
		}

        public CSList<ObjectType> FilteredBy(string filter)
        {
			return FilteredBy(new CSFilter(filter));
        }

		public CSList<ObjectType> FilteredBy(string filter, CSParameterCollection parameters)
		{
			return FilteredBy(new CSFilter(filter, parameters));
		}

		public CSList<ObjectType> FilteredBy(string filter, params CSParameter[] parameters)
		{
			return FilteredBy(new CSFilter(filter, parameters));
		}

		public CSList<ObjectType> FilteredBy(string filter, string paramName, object paramValue)
		{
			return FilteredBy(new CSFilter(filter,paramName,paramValue));
		}

		public CSList<ObjectType> FilteredBy(string filter, string paramName1, object paramValue1, string paramName2, object paramValue2)
		{
			return FilteredBy(new CSFilter(filter, paramName1, paramValue1,paramName2,paramValue2));
		}

		public CSList<ObjectType> FilteredBy(string filter, string paramName1, object paramValue1, string paramName2, object paramValue2, string paramName3, object paramValue3)
		{
			return FilteredBy(new CSFilter(filter, paramName1, paramValue1, paramName2, paramValue2, paramName3, paramValue3));
		}

		public CSList<ObjectType> FilteredBy(Predicate<ObjectType> predicate)
        {
            CSList<ObjectType> newCollection = (CSList<ObjectType>)Activator.CreateInstance(GetType());

            newCollection._orderBy = _orderBy;
            newCollection._maxRecords = _maxRecords;
		    newCollection._startRecord = _startRecord;
            newCollection._filter = _filter;
            newCollection._filterPredicate = _filterPredicate;
            newCollection._filterPredicate += predicate;
            newCollection._relation = _relation;
            newCollection._relationObject = _relationObject;

            return newCollection;
        }

		public CSList<ObjectType> OrderedBy(string orderBy)
		{
			CSList<ObjectType> newCollection = (CSList<ObjectType>)Activator.CreateInstance(GetType());

			newCollection._orderBy = orderBy;
			newCollection._maxRecords = _maxRecords;
            newCollection._startRecord = _startRecord;
            newCollection._filterPredicate = _filterPredicate;
            newCollection._filter = _filter;
			newCollection._relation = _relation;
			newCollection._relationObject = _relationObject;

			return newCollection;
		}

        public CSList<ObjectType> Range(int from, int numRecords)
        {
            CSList<ObjectType> newCollection = (CSList<ObjectType>)Activator.CreateInstance(GetType());

            newCollection._orderBy = _orderBy;
            newCollection._maxRecords = numRecords;
            newCollection._startRecord = from;
            newCollection._filterPredicate = _filterPredicate;
            newCollection._filter = _filter;
            newCollection._relation = _relation;
            newCollection._relationObject = _relationObject;

            return newCollection;
        }

        public CSList<ObjectType> LimitTo(int numRecords)
        {
            CSList<ObjectType> newCollection = (CSList<ObjectType>)Activator.CreateInstance(GetType());

            newCollection._orderBy = _orderBy;
            newCollection._maxRecords = numRecords;
            newCollection._filterPredicate = _filterPredicate;
            newCollection._filter = _filter;
            newCollection._relation = _relation;
            newCollection._relationObject = _relationObject;

            return newCollection;
        }

	    protected ObjectType GetByKey(object key)
		{
            Populate();

			if (_objectMap != null)
			{
				if (_objectMap.ContainsKey(key))
					return _objectMap[key];
				else
					return null;
			}
			else
				return null;
		}

		public int Count
		{
			get
			{
				Populate();

				return _objectArray.Count;
			}
		}

		public int CountFast
		{
			get
			{
				return (int) GetScalar("*", CSAggregate.Count);
			}
		}

		public int MaxRecords
		{
			get
			{
				return _maxRecords;
			}
			set
			{
				_maxRecords = value;
				
				Refresh();
			}
		}

		public string OrderBy
		{
			get
			{
				return _orderBy;
			}
			set
			{
				_orderBy = value;
				
				Refresh();
			}
		}

		public bool HasObjects
		{
			get
			{
                if (_populated)
                    return Count > 0;
                else
                    return CountFast > 0;
			}
		}

		internal override void SetRelation(CSObject relationObject , CSRelation relation)
		{
			_relationObject = relationObject;
			_relation = relation;
		}

		public void AddFilter(CSFilter filter)
		{
			_filter &= filter;

			Refresh();
		}

		public void AddFilterOr(CSFilter filter)
		{
			_filter |= filter;

			Refresh();
		}

		public void AddFilter(string filterExpression)
		{
			AddFilter(new CSFilter(filterExpression));
		}

		public void AddFilterOr(string filterExpression)
		{
			AddFilterOr(new CSFilter(filterExpression));
		}

		public void AddFilter(string filterExpression , CSParameterCollection parameters)
		{
			AddFilter(new CSFilter(filterExpression,parameters));
		}

		public void AddFilter(string filterExpression , string paramName, object paramValue)
		{
			AddFilter(new CSFilter(filterExpression, paramName, paramValue));
		}

		public void AddFilterOr(string filterExpression , CSParameterCollection parameters)
		{
			AddFilterOr(new CSFilter(filterExpression, parameters));
		}

        public void AddFilterOr(string filterExpression, string paramName, object paramValue)
		{
			AddFilterOr(new CSFilter(filterExpression, paramName, paramValue));
		}

		public void Refresh()
		{
			_populated = false;
			_objectArray = null;
			_objectMap = null;

			if (ListChanged != null)
				ListChanged(this , new ListChangedEventArgs(ListChangedType.Reset,-1));
		}

		public override bool Save()
		{
			if (!_populated)
				return true;

			using (CSTransaction csTransaction = new CSTransaction(_schema , IsolationLevel.ReadUncommitted))
			{
				UpdateForeignKeys();

				foreach (ObjectType obj in _objectArray.ToArray())
				{
					if (obj.IsDirty)
						if (!obj.Save())
							return false;
				}

				if (_relation != null && _relation.PureManyToMany)
				{
					if (_removedObjects != null)
					{
						foreach (ObjectType obj in _removedObjects)
						{
							CSParameterCollection parameters = new CSParameterCollection();

							parameters.Add("@LocalKey").Value = _relationObject.Data["#" + _relation.LocalKey].Value;
							parameters.Add("@ForeignKey").Value = obj.Data["#" + _relation.ForeignKey].Value;

							string deleteSql = _schema.DB.BuildDeleteSQL(_relation.LinkTable, null, _schema.DB.QuoteField(_relation.LocalLinkKey) + "=@LocalKey and " + _schema.DB.QuoteField(_relation.ForeignLinkKey) + "=@ForeignKey");

							_schema.DB.ExecuteNonQuery(deleteSql, parameters);
						}

						_removedObjects = null;
					}

					if (_addedObjects != null)
					{
						foreach (ObjectType obj in _addedObjects)
						{
							CSParameterCollection parameters = new CSParameterCollection();

							parameters.Add("@LocalKey").Value = _relationObject.Data["#" + _relation.LocalKey].Value;
							parameters.Add("@ForeignKey").Value = obj.Data["#" + _relation.ForeignKey].Value;

							string insertSql =
								_schema.DB.BuildInsertSQL(_relation.LinkTable, new string[] {_relation.LocalLinkKey, _relation.ForeignLinkKey},
								                          new string[] {"@LocalKey", "@ForeignKey"},null,null);

							_schema.DB.ExecuteNonQuery(insertSql, parameters);
						}
					}
				}

				csTransaction.Commit();
				
				return true;
			}
		}

		public ObjectType UniqueItem
		{
			get
			{
				Populate();

				if (Count > 1)
					throw new CSException("UniqueItem expects 0 or 1 items in list");
				else if (Count == 0)
					return null;
				else
					return _objectArray[0];
			}
		}

		public ObjectType FirstItem
		{
			get
			{
				Populate();

				if (Count == 0)
					return null;
				else
					return _objectArray[0];
			}
		}

		public object GetScalar(string fieldName , CSAggregate aggregate)
		{
			return GetScalar(fieldName , aggregate , null, null);
		}

		public object GetScalar(string fieldName , string orderBy)
		{
			return GetScalar(fieldName, orderBy, null, null);
		}

        public object GetScalar(string fieldName, string orderBy, string filterExpression)
        {
            return GetScalar(fieldName, orderBy, filterExpression, null);
        }

		public object GetScalar(string fieldName, string orderBy, string filterExpression, string paramName, object  paramValue)
		{
			return GetScalar(fieldName, orderBy, filterExpression, new CSParameterCollection(paramName, paramValue));
		}

		public object GetScalar(string fieldName, string orderBy, string filterExpression, CSParameterCollection filterParameters)
		{
            string tableAlias = CSHelper.NextTableAlias;

			CSFilter queryFilter = _filter.And(BuildRelationFilter(tableAlias));

			if (filterExpression != null && filterExpression.Length > 0)
			{
				queryFilter = queryFilter.And(filterExpression, filterParameters);
			}

			return CSObject<ObjectType>.GetScalar(fieldName, tableAlias, orderBy, queryFilter);
		}

		public object GetScalar(string fieldName , CSAggregate aggregate , string filterExpression , CSParameterCollection filterParameters)
		{
            string tableAlias = CSHelper.NextTableAlias;
            
            CSFilter queryFilter = _filter.And(BuildRelationFilter(tableAlias));

			if (filterExpression != null && filterExpression.Length > 0)
			{
				queryFilter = queryFilter.And(filterExpression , filterParameters);
			}
            
			return CSObject<ObjectType>.GetScalar(fieldName , tableAlias, aggregate , queryFilter);
		}

        public object GetScalar(string fieldName, CSAggregate aggregate, string filterExpression)
        {
            return GetScalar(fieldName, aggregate, filterExpression, null);
        }

		public object GetScalar(string fieldName , CSAggregate aggregate , string filterExpression , string paramName, object paramValue)
		{
			return GetScalar(fieldName,aggregate,filterExpression,new CSParameterCollection(paramName, paramValue));
		}

		public bool DeleteAll()
		{
			Populate();

			List<ObjectType> toDeleteList = new List<ObjectType>();

			foreach (ObjectType obj in _objectArray)
				toDeleteList.Add(obj);

			foreach (ObjectType obj in toDeleteList)
				if (!obj.Delete())
					return false;

			return true;
		}

        public bool DeleteAll(Predicate<ObjectType> predicate)
        {
            Populate();

            List<ObjectType> toDeleteList = new List<ObjectType>();

            foreach (ObjectType obj in _objectArray)
                if (predicate(obj))
                    toDeleteList.Add(obj);

            foreach (ObjectType obj in toDeleteList)
                if (!obj.Delete())
                    return false;

            return true;
        }


		public ObjectType AddNew()
		{
            ObjectType obj = CSObject<ObjectType>.New();

			Add(obj);

			return obj;
		}

		public void Add(ObjectType obj)
		{
			Populate();

			if (_relation != null && (_relation.RelationType == CSSchemaRelationType.ManyToMany && !_relation.PureManyToMany))
			    throw new NotSupportedException("CSList.Add() not supported for non-pure Many-To-Many relations");

			if (_relation != null && _relation.PureManyToMany)
			{
				if (_addedObjects == null)
					_addedObjects = new List<ObjectType>();

				_addedObjects.Add(obj);
			}

			obj.ObjectDeleted += OnObjectDeleted;

			_objectArray.Add(obj);

			if (ListChanged != null)
				ListChanged(this , new ListChangedEventArgs(ListChangedType.ItemAdded,_objectArray.Count-1));
		}

		public void Remove(ObjectType value)
		{
			Populate();

			int idx = _objectArray.IndexOf(value);

			if (idx >= 0)
				RemoveAt(idx);
		}


		private CSDataProvider DB
		{
			get
			{
				return _schema.DB;
			}
		}

		public void ForEach(Action<ObjectType> action)
		{
			Populate();

			foreach (ObjectType obj in _objectArray)
				action(obj);
		}

		public ObjectType Find(ObjectType obj)
		{
			int idx = IndexOf(obj);

			if (idx >= 0)
				return this[idx];
			else
				return null;
		}

	    public ObjectType Find(Predicate<ObjectType> predicate)
	    {
            Populate();
	        
            return _objectArray.Find(predicate);
	    }

		public bool Contains(Predicate<ObjectType> predicate)
		{
			return Find(predicate) != null;
		}

		private CSFilter BuildRelationFilter(string tableAlias)
		{
            if (tableAlias == null)
            {
                tableAlias = "";
            }
            else
                tableAlias += ".";

			if (_relation != null)
			{
                CSParameterCollection parameters = new CSParameterCollection();

				switch (_relation.RelationType)
				{
					case CSSchemaRelationType.OneToMany:
					{
						CSParameter csParameter = parameters.Add();

						csParameter.Value = _relationObject.Data["#" + _relation.LocalKey].Value;						

                        return new CSFilter("{" + tableAlias + _relation.ForeignKey + "}=" + csParameter.Name , parameters);
					}

					case CSSchemaRelationType.ManyToMany:
					{
						if (_relation.ForeignKey == null)
							_relation.ForeignKey = CSSchema.Get(typeof(ObjectType)).KeyColumns[0].Name;

						if (_relation.ForeignLinkKey == null)
							_relation.ForeignLinkKey = _relation.ForeignKey;

						CSParameter csParameter = parameters.Add();

						csParameter.Value = _relationObject.Data["#" + _relation.LocalKey].Value;

                        return new CSFilter("{" + tableAlias + _relation.ForeignKey + "} $in ($select {" + _relation.ForeignLinkKey + "} $from [" + _relation.LinkTable + "] where {" + _relation.LocalLinkKey + "}=" + csParameter.Name + ")" , parameters);
					}
				}
			}

			return CSFilter.None;
		}

		private void Populate()
		{
			if (_populated)
				return;

            if (_relation != null && _relationObject != null && _relation.RelationType == CSSchemaRelationType.OneToMany && _relationObject.IsNew)
            {
                _objectArray = new List<ObjectType>();
                _populated = true;

                return;
            }

			string mainAlias = CSHelper.NextTableAlias;

		    List<string> columnList = new List<string>(_schema.ColumnsToRead.Count);
            List<string> aliasList = new List<string>(_schema.ColumnsToRead.Count);
            Dictionary<string,string> aliasMap = new Dictionary<string, string>();

            foreach (string columnName in _schema.ColumnsToRead)
            {
                string alias = CSHelper.NextFieldAlias; 

                columnList.Add(mainAlias + "." + columnName);
                aliasList.Add(alias);
                aliasMap.Add(alias,columnName);
            }

		    CSJoinList filterJoins = new CSJoinList();

			string parsedFilterExpression = CSExpressionParser.ParseFilter(_filter.Expression, _schema, mainAlias, filterJoins);
			string parsedOrderBy = CSExpressionParser.ParseOrderBy(_orderBy, _schema, mainAlias, filterJoins);

            CSFilter whereFilter = new CSFilter(parsedFilterExpression, _filter.Parameters);
			CSFilter relationFilter = BuildRelationFilter(mainAlias);

            whereFilter = whereFilter.And(CSExpressionParser.ParseFilter(relationFilter.Expression,_schema,mainAlias,filterJoins),relationFilter.Parameters);

			string sqlQuery = _schema.DB.BuildSelectSQL(_schema.TableName, mainAlias, columnList.ToArray(), aliasList.ToArray(), filterJoins.BuildJoinExpressions(),whereFilter.Expression, parsedOrderBy,_maxRecords, true);

			_objectArray = GetObjects(sqlQuery,whereFilter.Parameters, aliasMap);

			if (_schema.KeyColumns.Count == 1)
			{
				string columnName = _schema.KeyColumns[0].Name;

                _objectMap = new Dictionary<object, ObjectType>();
			    
				foreach (ObjectType csObject in _objectArray)
					_objectMap.Add(csObject.Data["#" + columnName].Value , csObject);
			}

			_populated = true;
		}
        
		private List<ObjectType> GetObjects(string sqlQuery, CSParameterCollection parameters, Dictionary<string, string> aliasMap)
		{
			using (CSTransaction csTransaction = new CSTransaction(_schema))
			{
				List<ObjectType> objectList = new List<ObjectType>();

				using (IDataReader reader = DB.CreateReader(sqlQuery, parameters))
				{
					while (reader.Read())
					{
						ObjectType csObject = CSObject<ObjectType>.New();

						csObject.Fire_ObjectReading();

						csObject.FromDataReader(reader, aliasMap);

                        if (_filterPredicate != null)
                        {
                            bool shouldAdd = true;
                            
                            foreach (Predicate<ObjectType> predicate in _filterPredicate.GetInvocationList())
                                if (!predicate(csObject))
                                {
                                    shouldAdd = false;
                                    break;
                                }
                            
                            if (!shouldAdd)
                                continue;
                        }
					    
						csObject.ObjectDeleted += OnObjectDeleted;

						objectList.Add(csObject);

						csObject.Fire_ObjectRead();
					}
				}

				csTransaction.Commit();
				
				return objectList;
			}
		}

		internal override void UpdateForeignKeys()
		{
			if (!_populated || _relation == null)
				return;

			foreach (ObjectType obj in _objectArray)
			{
				CSFieldValue parentValue = _relationObject.Data["#" + _relation.LocalKey];
				CSFieldValue thisValue   = obj.Data["#" + _relation.ForeignKey];
				
				if (obj.IsNew || thisValue.Value == null || !thisValue.Value.Equals(parentValue.Value))
				{
					if (_relation.RelationType == CSSchemaRelationType.OneToMany)
						thisValue.Value = parentValue.Value;
				}
			}
		}

		private void OnObjectDeleted(ObjectType sender, EventArgs e)
		{
			int idx = _objectArray.IndexOf(sender);

			if (idx >= 0)
				_objectArray.RemoveAt(idx);
		}

        public ObjectType[] ToArray()
        {
            Populate();

            return _objectArray.ToArray();
        }

        public T[] ToArray<T>(CSToArrayIterator<ObjectType,T> iterator) where T:new()
        {
			Populate();

            T[] array = new T[Count];

            for (int i = 0; i < Count; i++)
            {
                array[i] = new T();

                iterator(_objectArray[i], ref array[i]);
            }

            return array;
        }

		public List<ObjectType> ToList()
		{
			Populate();

			return new List<ObjectType>(_objectArray);
		}

        #region IEnumerable Members

        public IEnumerator GetEnumerator()
		{
			Populate();

			return _objectArray.GetEnumerator();
		}

		#endregion

		#region IList Members

		public bool IsReadOnly
		{
			get
			{
				return false;
			}
		}

		object IList.this[int index]
		{
			get
			{
				Populate();

				return _objectArray[index];
			}
			set
			{
				throw new CSException("Items in CSCollections can not be set");
			}
		}

		public void RemoveAt(int index)
		{
			Populate();

			if (_relation != null && _relation.PureManyToMany)
			{
				if (_removedObjects == null)
					_removedObjects = new List<ObjectType>();

				_removedObjects.Add(_objectArray[index]);
			}

			_objectArray.RemoveAt(index);

			if (ListChanged != null)
				ListChanged(this , new ListChangedEventArgs(ListChangedType.ItemDeleted,index));
		}

		public void RemoveAll()
		{
			Populate();

			if (_relation != null && _relation.PureManyToMany)
			{
				_removedObjects = new List<ObjectType>(_objectArray);
			}

			_objectArray.Clear();
		}

        public void RemoveAll(Predicate<ObjectType> predicate)
        {
            Populate();

            if (_relation != null && _relation.PureManyToMany)
            {
                _removedObjects = new List<ObjectType>();

                foreach (ObjectType obj in _objectArray)
                    if (predicate(obj))
                        _removedObjects.Add(obj);

                foreach (ObjectType obj in _removedObjects)
                    _objectArray.Remove(obj);
            }
            else
            {
                _objectArray.RemoveAll(predicate);
            }
        }

		//public void Insert(int index , CSObject value)
		//{
		//    throw new NotSupportedException("Insert() not supported for CSList");
		//}

		public void Insert(int index,ObjectType value) {
			_objectArray.Insert(index, value);
		}

		void IList.Insert(int index, object value)
		{
			throw new NotSupportedException("Insert() not supported for CSList");
		}

        internal override void Remove(CSObject obj)
        {
            Remove((ObjectType) obj);
        }

		void IList.Remove(object value)
		{
			Remove((ObjectType)value);
		}

		public bool Contains(ObjectType value)
		{
			Populate();

			return _objectArray.Contains(value);
		}

		bool IList.Contains(object value)
		{
			Populate();

			return _objectArray.Contains((ObjectType)value);
		}

		public void Clear()
		{
			throw new NotSupportedException("Clear() not supported for CSList");
		}

		int IList.IndexOf(object value)
		{
			Populate();

			return _objectArray.IndexOf((ObjectType)value);
		}

		public int IndexOf(ObjectType value)
		{
			Populate();

			return _objectArray.IndexOf(value);
		}

		int IList.Add(object value)
		{
			Populate();

			if (value is ObjectType)
				Add((ObjectType) value);
			else
				throw new CSException("Add() only supported for objects of type <" + typeof(ObjectType).Name + ">");

			return _objectArray.Count-1;
		}

		public bool IsFixedSize
		{
			get
			{
				return false;
			}
		}

		#endregion

		#region ICollection Members

		public bool IsSynchronized
		{
			get
			{
				return false;
			}
		}

		public void CopyTo(ObjectType[] array , int index)
		{
			Populate();

			_objectArray.CopyTo(array, index);
		}

		void ICollection.CopyTo(Array array, int index)
		{
			Populate();

			_objectArray.CopyTo((ObjectType[])array, index);
		}

        public object SyncRoot
        {
            get
            {
				Populate();

				return _objectArray;
            }
        }

		#endregion

		#region ITypedList Members

		public PropertyDescriptorCollection GetItemProperties(PropertyDescriptor[] listAccessors)
		{
			List<PropertyDescriptor> descriptors = new List<PropertyDescriptor>();

            PropertyInfo[] properties = typeof(ObjectType).GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);

			foreach (PropertyInfo propertyInfo in properties)
				descriptors.Add(new CSFieldDescriptor(typeof(ObjectType), propertyInfo));

			return new PropertyDescriptorCollection(descriptors.ToArray());
		}

		public string GetListName(PropertyDescriptor[] listAccessors)
		{
            return "CSList_" + typeof(ObjectType).Name;
		}

		#endregion

		#region IBindingList Members

		public void AddIndex(PropertyDescriptor property)
		{
		}

		public bool AllowNew
		{
			get
			{
				return true;
			}
		}

		public void ApplySort(PropertyDescriptor property, ListSortDirection direction)
		{
			_orderBy = property.Name;
			
			if (direction == ListSortDirection.Descending) 
				_orderBy += "-";

			Refresh();
		}

		public PropertyDescriptor SortProperty
		{
			get
			{
				string[] arr = _orderBy.Split(' ');

				if (arr.Length > 0)
					return GetItemProperties(null)[arr[0]];
				else 
					return null;
			}
		}

		public int Find(PropertyDescriptor property, object key)
		{
			throw new NotSupportedException();
		}

		public bool SupportsSorting
		{
			get
			{
				return true;
			}
		}

		public bool IsSorted
		{
			get
			{
				return _orderBy.Length > 0;
			}
		}

		public bool AllowRemove
		{
			get
			{
				return true;
			}
		}

		public bool SupportsSearching
		{
			get
			{
				return false;
			}
		}

		public ListSortDirection SortDirection
		{
			get
			{
				return (_orderBy.ToLower().IndexOf(" desc") > 0) ? ListSortDirection.Descending:ListSortDirection.Ascending;
			}
		}

		public event ListChangedEventHandler ListChanged;

		public bool SupportsChangeNotification
		{
			get
			{
				return true;
			}
		}

		public void RemoveSort()
		{
			throw new NotSupportedException();
		}

		object IBindingList.AddNew()
		{
			return AddNew();
		}

		public bool AllowEdit
		{
			get
			{
				return true;
			}
		}

		public void RemoveIndex(PropertyDescriptor property)
		{
			throw new NotSupportedException();
		}

		#endregion

		#region IListSource Members

		public IList GetList()
		{
			return this;
		}

		public bool ContainsListCollection
		{
			get
			{
				return false;
			}
		}

		#endregion

		#region PropertyDescriptor

		private class CSFieldDescriptor : PropertyDescriptor
		{
		    readonly Type         _type;
		    readonly PropertyInfo _propertyInfo;

			public CSFieldDescriptor(Type type , PropertyInfo propertyInfo)
				: base(propertyInfo.Name,new Attribute[] {})
			{
				_type = type;
				_propertyInfo = propertyInfo;
			}

			public override object GetValue(object component)
			{
				if (!component.GetType().IsSubclassOf(_type))
					return null;

				return _propertyInfo.GetValue(component,null);
			}

			public override void SetValue(object component, object value)
			{
				if (!component.GetType().IsSubclassOf(_type))
					return;

				_propertyInfo.SetValue(component, value, null);
			}

			public override void ResetValue(object component)
			{
			}

			public override bool CanResetValue(object component)
			{
				return false;
			}

			public override Type PropertyType
			{
				get
				{
					return _propertyInfo.PropertyType;
				}
			}

			public override Type ComponentType
			{
				get
				{
					return _type;
				}
			}

			public override bool IsReadOnly
			{
				get
				{
					return !_propertyInfo.CanWrite;
				}
			}

			public override bool ShouldSerializeValue(object component)
			{
				return false;
			}
		}

		#endregion

		public event EventHandler Disposed;

		public void Dispose()
		{
			if (_objectArray != null)
				_objectArray.Clear();

			_objectArray = null;

			if (Disposed != null)
				Disposed(this,EventArgs.Empty);
		}


	    void IList<ObjectType>.Insert(int index, ObjectType item)
	    {
	        throw new NotImplementedException();
	    }

	    bool ICollection<ObjectType>.Remove(ObjectType item)
	    {
            Remove(item);

            return true;
	    }

	    IEnumerator<ObjectType> IEnumerable<ObjectType>.GetEnumerator()
	    {
            Populate();
	        
            return _objectArray.GetEnumerator();
	    }
	}
}
